3 * @defgroup Language Language
9 if( !defined( 'MEDIAWIKI' ) ) {
10 echo "This file is part of MediaWiki, it is not a valid entry point.\n";
15 global $wgLanguageNames;
16 require_once( dirname(__FILE__
) . '/Names.php' ) ;
18 global $wgInputEncoding, $wgOutputEncoding;
21 * These are always UTF-8, they exist only for backwards compatibility
23 $wgInputEncoding = "UTF-8";
24 $wgOutputEncoding = "UTF-8";
26 if( function_exists( 'mb_strtoupper' ) ) {
27 mb_internal_encoding('UTF-8');
31 * a fake language converter
37 function FakeConverter($langobj) {$this->mLang
= $langobj;}
38 function autoConvertToAllVariants($text) {return $text;}
39 function convert($t, $i) {return $t;}
40 function parserConvert($t, $p) {return $t;}
41 function getVariants() { return array( $this->mLang
->getCode() ); }
42 function getPreferredVariant() {return $this->mLang
->getCode(); }
43 function findVariantLink(&$l, &$n, $ignoreOtherCond = false) {}
44 function getExtraHashOptions() {return '';}
45 function getParsedTitle() {return '';}
46 function markNoConversion($text, $noParse=false) {return $text;}
47 function convertCategoryKey( $key ) {return $key; }
48 function convertLinkToAllVariants($text){ return array( $this->mLang
->getCode() => $text); }
49 function armourMath($text){ return $text; }
50 function groupConvert($group) {return '';}
54 * Internationalisation code
58 var $mConverter, $mVariants, $mCode, $mLoaded = false;
59 var $mMagicExtensions = array(), $mMagicHookDone = false;
61 static public $mLocalisationKeys = array(
62 'fallback', 'namespaceNames', 'mathNames', 'bookstoreList',
63 'magicWords', 'messages', 'rtl', 'digitTransformTable',
64 'separatorTransformTable', 'fallback8bitEncoding', 'linkPrefixExtension',
65 'defaultUserOptionOverrides', 'linkTrail', 'namespaceAliases',
66 'dateFormats', 'datePreferences', 'datePreferenceMigrationMap',
67 'defaultDateFormat', 'extraUserToggles', 'specialPageAliases',
71 static public $mMergeableMapKeys = array( 'messages', 'namespaceNames', 'mathNames',
72 'dateFormats', 'defaultUserOptionOverrides', 'magicWords', 'imageFiles' );
74 static public $mMergeableListKeys = array( 'extraUserToggles' );
76 static public $mMergeableAliasListKeys = array( 'specialPageAliases' );
78 static public $mLocalisationCache = array();
79 static public $mLangObjCache = array();
81 static public $mWeekdayMsgs = array(
82 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
86 static public $mWeekdayAbbrevMsgs = array(
87 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
90 static public $mMonthMsgs = array(
91 'january', 'february', 'march', 'april', 'may_long', 'june',
92 'july', 'august', 'september', 'october', 'november',
95 static public $mMonthGenMsgs = array(
96 'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
97 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
100 static public $mMonthAbbrevMsgs = array(
101 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
102 'sep', 'oct', 'nov', 'dec'
105 static public $mIranianCalendarMonthMsgs = array(
106 'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3',
107 'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6',
108 'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9',
109 'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12'
112 static public $mHebrewCalendarMonthMsgs = array(
113 'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3',
114 'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6',
115 'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9',
116 'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12',
117 'hebrew-calendar-m6a', 'hebrew-calendar-m6b'
120 static public $mHebrewCalendarMonthGenMsgs = array(
121 'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen',
122 'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen',
123 'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen',
124 'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen',
125 'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen'
128 static public $mHijriCalendarMonthMsgs = array(
129 'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3',
130 'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6',
131 'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9',
132 'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12'
136 * Get a cached language object for a given language code
138 static function factory( $code ) {
139 if ( !isset( self
::$mLangObjCache[$code] ) ) {
140 if( count( self
::$mLangObjCache ) > 10 ) {
141 // Don't keep a billion objects around, that's stupid.
142 self
::$mLangObjCache = array();
144 self
::$mLangObjCache[$code] = self
::newFromCode( $code );
146 return self
::$mLangObjCache[$code];
150 * Create a language object for a given language code
152 protected static function newFromCode( $code ) {
154 static $recursionLevel = 0;
155 if ( $code == 'en' ) {
158 $class = 'Language' . str_replace( '-', '_', ucfirst( $code ) );
159 // Preload base classes to work around APC/PHP5 bug
160 if ( file_exists( "$IP/languages/classes/$class.deps.php" ) ) {
161 include_once("$IP/languages/classes/$class.deps.php");
163 if ( file_exists( "$IP/languages/classes/$class.php" ) ) {
164 include_once("$IP/languages/classes/$class.php");
168 if ( $recursionLevel > 5 ) {
169 throw new MWException( "Language fallback loop detected when creating class $class\n" );
172 if( ! class_exists( $class ) ) {
173 $fallback = Language
::getFallbackFor( $code );
175 $lang = Language
::newFromCode( $fallback );
177 $lang->setCode( $code );
184 function __construct() {
185 $this->mConverter
= new FakeConverter($this);
186 // Set the code to the name of the descendant
187 if ( get_class( $this ) == 'Language' ) {
190 $this->mCode
= str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) );
195 * Reduce memory usage
197 function __destruct() {
198 foreach ( $this as $name => $value ) {
199 unset( $this->$name );
204 * Hook which will be called if this is the content language.
205 * Descendants can use this to register hook functions or modify globals
207 function initContLang() {}
210 * @deprecated Use User::getDefaultOptions()
213 function getDefaultUserOptions() {
214 wfDeprecated( __METHOD__
);
215 return User
::getDefaultOptions();
218 function getFallbackLanguageCode() {
219 return self
::getFallbackFor( $this->mCode
);
223 * Exports $wgBookstoreListEn
226 function getBookstoreList() {
228 return $this->bookstoreList
;
234 function getNamespaces() {
236 return $this->namespaceNames
;
240 * A convenience function that returns the same thing as
241 * getNamespaces() except with the array values changed to ' '
242 * where it found '_', useful for producing output to be displayed
243 * e.g. in <select> forms.
247 function getFormattedNamespaces() {
248 $ns = $this->getNamespaces();
249 foreach($ns as $k => $v) {
250 $ns[$k] = strtr($v, '_', ' ');
256 * Get a namespace value by key
258 * $mw_ns = $wgContLang->getNsText( NS_MEDIAWIKI );
259 * echo $mw_ns; // prints 'MediaWiki'
262 * @param $index Int: the array key of the namespace to return
263 * @return mixed, string if the namespace value exists, otherwise false
265 function getNsText( $index ) {
266 $ns = $this->getNamespaces();
267 return isset( $ns[$index] ) ?
$ns[$index] : false;
271 * A convenience function that returns the same thing as
272 * getNsText() except with '_' changed to ' ', useful for
277 function getFormattedNsText( $index ) {
278 $ns = $this->getNsText( $index );
279 return strtr($ns, '_', ' ');
283 * Get a namespace key by value, case insensitive.
284 * Only matches namespace names for the current language, not the
285 * canonical ones defined in Namespace.php.
287 * @param $text String
288 * @return mixed An integer if $text is a valid value otherwise false
290 function getLocalNsIndex( $text ) {
292 $lctext = $this->lc($text);
293 return isset( $this->mNamespaceIds
[$lctext] ) ?
$this->mNamespaceIds
[$lctext] : false;
297 * Get a namespace key by value, case insensitive. Canonical namespace
298 * names override custom ones defined for the current language.
300 * @param $text String
301 * @return mixed An integer if $text is a valid value otherwise false
303 function getNsIndex( $text ) {
305 $lctext = $this->lc($text);
306 if( ( $ns = MWNamespace
::getCanonicalIndex( $lctext ) ) !== null ) return $ns;
307 return isset( $this->mNamespaceIds
[$lctext] ) ?
$this->mNamespaceIds
[$lctext] : false;
311 * short names for language variants used for language conversion links.
313 * @param $code String
316 function getVariantname( $code ) {
317 return $this->getMessageFromDB( "variantname-$code" );
320 function specialPage( $name ) {
321 $aliases = $this->getSpecialPageAliases();
322 if ( isset( $aliases[$name][0] ) ) {
323 $name = $aliases[$name][0];
325 return $this->getNsText( NS_SPECIAL
) . ':' . $name;
328 function getQuickbarSettings() {
330 $this->getMessage( 'qbsettings-none' ),
331 $this->getMessage( 'qbsettings-fixedleft' ),
332 $this->getMessage( 'qbsettings-fixedright' ),
333 $this->getMessage( 'qbsettings-floatingleft' ),
334 $this->getMessage( 'qbsettings-floatingright' )
338 function getMathNames() {
340 return $this->mathNames
;
343 function getDatePreferences() {
345 return $this->datePreferences
;
348 function getDateFormats() {
350 return $this->dateFormats
;
353 function getDefaultDateFormat() {
355 return $this->defaultDateFormat
;
358 function getDatePreferenceMigrationMap() {
360 return $this->datePreferenceMigrationMap
;
363 function getImageFile( $image ) {
365 return $this->imageFiles
[$image];
368 function getDefaultUserOptionOverrides() {
370 # XXX - apparently some languageas get empty arrays, didn't get to it yet -- midom
371 if (is_array($this->defaultUserOptionOverrides
)) {
372 return $this->defaultUserOptionOverrides
;
378 function getExtraUserToggles() {
380 return $this->extraUserToggles
;
383 function getUserToggle( $tog ) {
384 return $this->getMessageFromDB( "tog-$tog" );
388 * Get language names, indexed by code.
389 * If $customisedOnly is true, only returns codes with a messages file
391 public static function getLanguageNames( $customisedOnly = false ) {
392 global $wgLanguageNames, $wgExtraLanguageNames;
393 $allNames = $wgExtraLanguageNames +
$wgLanguageNames;
394 if ( !$customisedOnly ) {
400 $dir = opendir( "$IP/languages/messages" );
401 while( false !== ( $file = readdir( $dir ) ) ) {
403 if( preg_match( '/Messages([A-Z][a-z_]+)\.php$/', $file, $m ) ) {
404 $code = str_replace( '_', '-', strtolower( $m[1] ) );
405 if ( isset( $allNames[$code] ) ) {
406 $names[$code] = $allNames[$code];
415 * Get a message from the MediaWiki namespace.
417 * @param $msg String: message name
420 function getMessageFromDB( $msg ) {
421 return wfMsgExt( $msg, array( 'parsemag', 'language' => $this ) );
424 function getLanguageName( $code ) {
425 $names = self
::getLanguageNames();
426 if ( !array_key_exists( $code, $names ) ) {
429 return $names[$code];
432 function getMonthName( $key ) {
433 return $this->getMessageFromDB( self
::$mMonthMsgs[$key-1] );
436 function getMonthNameGen( $key ) {
437 return $this->getMessageFromDB( self
::$mMonthGenMsgs[$key-1] );
440 function getMonthAbbreviation( $key ) {
441 return $this->getMessageFromDB( self
::$mMonthAbbrevMsgs[$key-1] );
444 function getWeekdayName( $key ) {
445 return $this->getMessageFromDB( self
::$mWeekdayMsgs[$key-1] );
448 function getWeekdayAbbreviation( $key ) {
449 return $this->getMessageFromDB( self
::$mWeekdayAbbrevMsgs[$key-1] );
452 function getIranianCalendarMonthName( $key ) {
453 return $this->getMessageFromDB( self
::$mIranianCalendarMonthMsgs[$key-1] );
456 function getHebrewCalendarMonthName( $key ) {
457 return $this->getMessageFromDB( self
::$mHebrewCalendarMonthMsgs[$key-1] );
460 function getHebrewCalendarMonthNameGen( $key ) {
461 return $this->getMessageFromDB( self
::$mHebrewCalendarMonthGenMsgs[$key-1] );
464 function getHijriCalendarMonthName( $key ) {
465 return $this->getMessageFromDB( self
::$mHijriCalendarMonthMsgs[$key-1] );
469 * Used by date() and time() to adjust the time output.
471 * @param $ts Int the time in date('YmdHis') format
472 * @param $tz Mixed: adjust the time by this amount (default false, mean we
473 * get user timecorrection setting)
476 function userAdjust( $ts, $tz = false ) {
477 global $wgUser, $wgLocalTZoffset;
479 if ( $tz === false ) {
480 $tz = $wgUser->getOption( 'timecorrection' );
483 $data = explode( '|', $tz, 3 );
485 if ( $data[0] == 'ZoneInfo' ) {
486 if ( function_exists( 'timezone_open' ) && @timezone_open
( $data[2] ) !== false ) {
487 $date = date_create( $ts, timezone_open( 'UTC' ) );
488 date_timezone_set( $date, timezone_open( $data[2] ) );
489 $date = date_format( $date, 'YmdHis' );
492 # Unrecognized timezone, default to 'Offset' with the stored offset.
497 if ( $data[0] == 'System' ||
$tz == '' ) {
498 # Global offset in minutes.
499 if( isset($wgLocalTZoffset) ) $minDiff = $wgLocalTZoffset;
500 } else if ( $data[0] == 'Offset' ) {
501 $minDiff = intval( $data[1] );
503 $data = explode( ':', $tz );
504 if( count( $data ) == 2 ) {
505 $data[0] = intval( $data[0] );
506 $data[1] = intval( $data[1] );
507 $minDiff = abs( $data[0] ) * 60 +
$data[1];
508 if ( $data[0] < 0 ) $minDiff = -$minDiff;
510 $minDiff = intval( $data[0] ) * 60;
514 # No difference ? Return time unchanged
515 if ( 0 == $minDiff ) return $ts;
517 wfSuppressWarnings(); // E_STRICT system time bitching
518 # Generate an adjusted date; take advantage of the fact that mktime
519 # will normalize out-of-range values so we don't have to split $minDiff
520 # into hours and minutes.
522 (int)substr( $ts, 8, 2) ), # Hours
523 (int)substr( $ts, 10, 2 ) +
$minDiff, # Minutes
524 (int)substr( $ts, 12, 2 ), # Seconds
525 (int)substr( $ts, 4, 2 ), # Month
526 (int)substr( $ts, 6, 2 ), # Day
527 (int)substr( $ts, 0, 4 ) ); #Year
529 $date = date( 'YmdHis', $t );
536 * This is a workalike of PHP's date() function, but with better
537 * internationalisation, a reduced set of format characters, and a better
540 * Supported format characters are dDjlNwzWFmMntLoYyaAgGhHiscrU. See the
541 * PHP manual for definitions. "o" format character is supported since
542 * PHP 5.1.0, previous versions return literal o.
543 * There are a number of extensions, which start with "x":
545 * xn Do not translate digits of the next numeric format character
546 * xN Toggle raw digit (xn) flag, stays set until explicitly unset
547 * xr Use roman numerals for the next numeric format character
548 * xh Use hebrew numerals for the next numeric format character
550 * xg Genitive month name
552 * xij j (day number) in Iranian calendar
553 * xiF F (month name) in Iranian calendar
554 * xin n (month number) in Iranian calendar
555 * xiY Y (full year) in Iranian calendar
557 * xjj j (day number) in Hebrew calendar
558 * xjF F (month name) in Hebrew calendar
559 * xjt t (days in month) in Hebrew calendar
560 * xjx xg (genitive month name) in Hebrew calendar
561 * xjn n (month number) in Hebrew calendar
562 * xjY Y (full year) in Hebrew calendar
564 * xmj j (day number) in Hijri calendar
565 * xmF F (month name) in Hijri calendar
566 * xmn n (month number) in Hijri calendar
567 * xmY Y (full year) in Hijri calendar
569 * xkY Y (full year) in Thai solar calendar. Months and days are
570 * identical to the Gregorian calendar
571 * xoY Y (full year) in Minguo calendar or Juche year.
572 * Months and days are identical to the
574 * xtY Y (full year) in Japanese nengo. Months and days are
575 * identical to the Gregorian calendar
577 * Characters enclosed in double quotes will be considered literal (with
578 * the quotes themselves removed). Unmatched quotes will be considered
579 * literal quotes. Example:
581 * "The month is" F => The month is January
584 * Backslash escaping is also supported.
586 * Input timestamp is assumed to be pre-normalized to the desired local
589 * @param $format String
590 * @param $ts String: 14-character timestamp
593 * @todo emulation of "o" format character for PHP pre 5.1.0
594 * @todo handling of "o" format character for Iranian, Hebrew, Hijri & Thai?
596 function sprintfDate( $format, $ts ) {
609 for ( $p = 0; $p < strlen( $format ); $p++
) {
612 if ( $code == 'x' && $p < strlen( $format ) - 1 ) {
613 $code .= $format[++
$p];
616 if ( ( $code === 'xi' ||
$code == 'xj' ||
$code == 'xk' ||
$code == 'xm' ||
$code == 'xo' ||
$code == 'xt' ) && $p < strlen( $format ) - 1 ) {
617 $code .= $format[++
$p];
628 $rawToggle = !$rawToggle;
637 $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
640 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
641 $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
644 $num = substr( $ts, 6, 2 );
647 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
648 $s .= $this->getWeekdayAbbreviation( gmdate( 'w', $unix ) +
1 );
651 $num = intval( substr( $ts, 6, 2 ) );
654 if ( !$iranian ) $iranian = self
::tsToIranian( $ts );
658 if ( !$hijri ) $hijri = self
::tsToHijri( $ts );
662 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
666 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
667 $s .= $this->getWeekdayName( gmdate( 'w', $unix ) +
1 );
670 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
671 $w = gmdate( 'w', $unix );
675 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
676 $num = gmdate( 'w', $unix );
679 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
680 $num = gmdate( 'z', $unix );
683 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
684 $num = gmdate( 'W', $unix );
687 $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
690 if ( !$iranian ) $iranian = self
::tsToIranian( $ts );
691 $s .= $this->getIranianCalendarMonthName( $iranian[1] );
694 if ( !$hijri ) $hijri = self
::tsToHijri( $ts );
695 $s .= $this->getHijriCalendarMonthName( $hijri[1] );
698 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
699 $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
702 $num = substr( $ts, 4, 2 );
705 $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
708 $num = intval( substr( $ts, 4, 2 ) );
711 if ( !$iranian ) $iranian = self
::tsToIranian( $ts );
715 if ( !$hijri ) $hijri = self
::tsToHijri ( $ts );
719 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
723 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
724 $num = gmdate( 't', $unix );
727 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
731 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
732 $num = gmdate( 'L', $unix );
734 # 'o' is supported since PHP 5.1.0
735 # return literal if not supported
736 # TODO: emulation for pre 5.1.0 versions
738 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
739 if ( version_compare(PHP_VERSION
, '5.1.0') === 1 )
740 $num = date( 'o', $unix );
745 $num = substr( $ts, 0, 4 );
748 if ( !$iranian ) $iranian = self
::tsToIranian( $ts );
752 if ( !$hijri ) $hijri = self
::tsToHijri( $ts );
756 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
760 if ( !$thai ) $thai = self
::tsToYear( $ts, 'thai' );
764 if ( !$minguo ) $minguo = self
::tsToYear( $ts, 'minguo' );
768 if ( !$tenno ) $tenno = self
::tsToYear( $ts, 'tenno' );
772 $num = substr( $ts, 2, 2 );
775 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ?
'am' : 'pm';
778 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ?
'AM' : 'PM';
781 $h = substr( $ts, 8, 2 );
782 $num = $h %
12 ?
$h %
12 : 12;
785 $num = intval( substr( $ts, 8, 2 ) );
788 $h = substr( $ts, 8, 2 );
789 $num = sprintf( '%02d', $h %
12 ?
$h %
12 : 12 );
792 $num = substr( $ts, 8, 2 );
795 $num = substr( $ts, 10, 2 );
798 $num = substr( $ts, 12, 2 );
801 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
802 $s .= gmdate( 'c', $unix );
805 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
806 $s .= gmdate( 'r', $unix );
809 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
814 if ( $p < strlen( $format ) - 1 ) {
822 if ( $p < strlen( $format ) - 1 ) {
823 $endQuote = strpos( $format, '"', $p +
1 );
824 if ( $endQuote === false ) {
825 # No terminating quote, assume literal "
828 $s .= substr( $format, $p +
1, $endQuote - $p - 1 );
832 # Quote at end of string, assume literal "
839 if ( $num !== false ) {
840 if ( $rawToggle ||
$raw ) {
843 } elseif ( $roman ) {
844 $s .= self
::romanNumeral( $num );
846 } elseif( $hebrewNum ) {
847 $s .= self
::hebrewNumeral( $num );
850 $s .= $this->formatNum( $num, true );
858 private static $GREG_DAYS = array( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
859 private static $IRANIAN_DAYS = array( 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 );
861 * Algorithm by Roozbeh Pournader and Mohammad Toossi to convert
862 * Gregorian dates to Iranian dates. Originally written in C, it
863 * is released under the terms of GNU Lesser General Public
864 * License. Conversion to PHP was performed by Niklas Laxström.
866 * Link: http://www.farsiweb.info/jalali/jalali.c
868 private static function tsToIranian( $ts ) {
869 $gy = substr( $ts, 0, 4 ) -1600;
870 $gm = substr( $ts, 4, 2 ) -1;
871 $gd = substr( $ts, 6, 2 ) -1;
873 # Days passed from the beginning (including leap years)
876 - floor(($gy+
99) / 100)
877 +
floor(($gy+
399) / 400);
880 // Add days of the past months of this year
881 for( $i = 0; $i < $gm; $i++
) {
882 $gDayNo +
= self
::$GREG_DAYS[$i];
886 if ( $gm > 1 && (($gy%4
===0 && $gy%100
!==0 ||
($gy%400
==0)))) {
890 // Days passed in current month
893 $jDayNo = $gDayNo - 79;
895 $jNp = floor($jDayNo / 12053);
898 $jy = 979 +
33*$jNp +
4*floor($jDayNo/1461);
901 if ( $jDayNo >= 366 ) {
902 $jy +
= floor(($jDayNo-1)/365);
903 $jDayNo = floor(($jDayNo-1)%365
);
906 for ( $i = 0; $i < 11 && $jDayNo >= self
::$IRANIAN_DAYS[$i]; $i++
) {
907 $jDayNo -= self
::$IRANIAN_DAYS[$i];
913 return array($jy, $jm, $jd);
916 * Converting Gregorian dates to Hijri dates.
918 * Based on a PHP-Nuke block by Sharjeel which is released under GNU/GPL license
920 * @link http://phpnuke.org/modules.php?name=News&file=article&sid=8234&mode=thread&order=0&thold=0
922 private static function tsToHijri ( $ts ) {
923 $year = substr( $ts, 0, 4 );
924 $month = substr( $ts, 4, 2 );
925 $day = substr( $ts, 6, 2 );
934 if (($zy>1582)||
(($zy==1582)&&($zm>10))||
(($zy==1582)&&($zm==10)&&($zd>14)))
938 $zjd=(int)((1461*($zy +
4800 +
(int)( ($zm-14) /12) ))/4) +
(int)((367*($zm-2-12*((int)(($zm-14)/12))))/12)-(int)((3*(int)(( ($zy+
4900+
(int)(($zm-14)/12))/100)))/4)+
$zd-32075;
942 $zjd = 367*$zy-(int)((7*($zy+
5001+
(int)(($zm-9)/7)))/4)+
(int)((275*$zm)/9)+
$zd+
1729777;
945 $zl=$zjd-1948440+
10632;
946 $zn=(int)(($zl-1)/10631);
947 $zl=$zl-10631*$zn+
354;
948 $zj=((int)((10985-$zl)/5316))*((int)((50*$zl)/17719))+
((int)($zl/5670))*((int)((43*$zl)/15238));
949 $zl=$zl-((int)((30-$zj)/15))*((int)((17719*$zj)/50))-((int)($zj/16))*((int)((15238*$zj)/43))+
29;
950 $zm=(int)((24*$zl)/709);
951 $zd=$zl-(int)((709*$zm)/24);
954 return array ($zy, $zm, $zd);
958 * Converting Gregorian dates to Hebrew dates.
960 * Based on a JavaScript code by Abu Mami and Yisrael Hersch
961 * (abu-mami@kaluach.net, http://www.kaluach.net), who permitted
962 * to translate the relevant functions into PHP and release them under
965 * The months are counted from Tishrei = 1. In a leap year, Adar I is 13
966 * and Adar II is 14. In a non-leap year, Adar is 6.
968 private static function tsToHebrew( $ts ) {
970 $year = substr( $ts, 0, 4 );
971 $month = substr( $ts, 4, 2 );
972 $day = substr( $ts, 6, 2 );
974 # Calculate Hebrew year
975 $hebrewYear = $year +
3760;
977 # Month number when September = 1, August = 12
986 # Calculate day of year from 1 September
988 for( $i = 1; $i < $month; $i++
) {
992 # Check if the year is leap
993 if( $year %
400 == 0 ||
( $year %
4 == 0 && $year %
100 > 0 ) ) {
996 } elseif( $i == 8 ||
$i == 10 ||
$i == 1 ||
$i == 3 ) {
1003 # Calculate the start of the Hebrew year
1004 $start = self
::hebrewYearStart( $hebrewYear );
1006 # Calculate next year's start
1007 if( $dayOfYear <= $start ) {
1008 # Day is before the start of the year - it is the previous year
1010 $nextStart = $start;
1014 # Add days since previous year's 1 September
1016 if( ( $year %
400 == 0 ) ||
( $year %
100 != 0 && $year %
4 == 0 ) ) {
1020 # Start of the new (previous) year
1021 $start = self
::hebrewYearStart( $hebrewYear );
1024 $nextStart = self
::hebrewYearStart( $hebrewYear +
1 );
1027 # Calculate Hebrew day of year
1028 $hebrewDayOfYear = $dayOfYear - $start;
1030 # Difference between year's days
1031 $diff = $nextStart - $start;
1032 # Add 12 (or 13 for leap years) days to ignore the difference between
1033 # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
1034 # difference is only about the year type
1035 if( ( $year %
400 == 0 ) ||
( $year %
100 != 0 && $year %
4 == 0 ) ) {
1041 # Check the year pattern, and is leap year
1042 # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
1043 # This is mod 30, to work on both leap years (which add 30 days of Adar I)
1044 # and non-leap years
1045 $yearPattern = $diff %
30;
1046 # Check if leap year
1047 $isLeap = $diff >= 30;
1049 # Calculate day in the month from number of day in the Hebrew year
1050 # Don't check Adar - if the day is not in Adar, we will stop before;
1051 # if it is in Adar, we will use it to check if it is Adar I or Adar II
1052 $hebrewDay = $hebrewDayOfYear;
1055 while( $hebrewMonth <= 12 ) {
1056 # Calculate days in this month
1057 if( $isLeap && $hebrewMonth == 6 ) {
1058 # Adar in a leap year
1060 # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
1062 if( $hebrewDay <= $days ) {
1066 # Subtract the days of Adar I
1067 $hebrewDay -= $days;
1070 if( $hebrewDay <= $days ) {
1076 } elseif( $hebrewMonth == 2 && $yearPattern == 2 ) {
1077 # Cheshvan in a complete year (otherwise as the rule below)
1079 } elseif( $hebrewMonth == 3 && $yearPattern == 0 ) {
1080 # Kislev in an incomplete year (otherwise as the rule below)
1083 # Odd months have 30 days, even have 29
1084 $days = 30 - ( $hebrewMonth - 1 ) %
2;
1086 if( $hebrewDay <= $days ) {
1087 # In the current month
1090 # Subtract the days of the current month
1091 $hebrewDay -= $days;
1092 # Try in the next month
1097 return array( $hebrewYear, $hebrewMonth, $hebrewDay, $days );
1101 * This calculates the Hebrew year start, as days since 1 September.
1102 * Based on Carl Friedrich Gauss algorithm for finding Easter date.
1103 * Used for Hebrew date.
1105 private static function hebrewYearStart( $year ) {
1106 $a = intval( ( 12 * ( $year - 1 ) +
17 ) %
19 );
1107 $b = intval( ( $year - 1 ) %
4 );
1108 $m = 32.044093161144 +
1.5542417966212 * $a +
$b / 4.0 - 0.0031777940220923 * ( $year - 1 );
1112 $Mar = intval( $m );
1118 $c = intval( ( $Mar +
3 * ( $year - 1 ) +
5 * $b +
5 ) %
7);
1119 if( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
1121 } else if( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
1123 } else if( $c == 2 ||
$c == 4 ||
$c == 6 ) {
1127 $Mar +
= intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
1132 * Algorithm to convert Gregorian dates to Thai solar dates,
1133 * Minguo dates or Minguo dates.
1135 * Link: http://en.wikipedia.org/wiki/Thai_solar_calendar
1136 * http://en.wikipedia.org/wiki/Minguo_calendar
1137 * http://en.wikipedia.org/wiki/Japanese_era_name
1139 * @param $ts String: 14-character timestamp, calender name
1140 * @return array converted year, month, day
1142 private static function tsToYear( $ts, $cName ) {
1143 $gy = substr( $ts, 0, 4 );
1144 $gm = substr( $ts, 4, 2 );
1145 $gd = substr( $ts, 6, 2 );
1147 if (!strcmp($cName,'thai')) {
1149 # Add 543 years to the Gregorian calendar
1150 # Months and days are identical
1151 $gy_offset = $gy +
543;
1152 } else if ((!strcmp($cName,'minguo')) ||
!strcmp($cName,'juche')) {
1154 # Deduct 1911 years from the Gregorian calendar
1155 # Months and days are identical
1156 $gy_offset = $gy - 1911;
1157 } else if (!strcmp($cName,'tenno')) {
1158 # Nengō dates up to Meiji period
1159 # Deduct years from the Gregorian calendar
1160 # depending on the nengo periods
1161 # Months and days are identical
1162 if (($gy < 1912) ||
(($gy == 1912) && ($gm < 7)) ||
(($gy == 1912) && ($gm == 7) && ($gd < 31))) {
1164 $gy_gannen = $gy - 1868 +
1;
1165 $gy_offset = $gy_gannen;
1166 if ($gy_gannen == 1)
1168 $gy_offset = '明治'.$gy_offset;
1169 } else if ((($gy == 1912) && ($gm == 7) && ($gd == 31)) ||
(($gy == 1912) && ($gm >= 8)) ||
(($gy > 1912) && ($gy < 1926)) ||
(($gy == 1926) && ($gm < 12)) ||
(($gy == 1926) && ($gm == 12) && ($gd < 26))) {
1171 $gy_gannen = $gy - 1912 +
1;
1172 $gy_offset = $gy_gannen;
1173 if ($gy_gannen == 1)
1175 $gy_offset = '大正'.$gy_offset;
1176 } else if ((($gy == 1926) && ($gm == 12) && ($gd >= 26)) ||
(($gy > 1926) && ($gy < 1989)) ||
(($gy == 1989) && ($gm == 1) && ($gd < 8))) {
1178 $gy_gannen = $gy - 1926 +
1;
1179 $gy_offset = $gy_gannen;
1180 if ($gy_gannen == 1)
1182 $gy_offset = '昭和'.$gy_offset;
1185 $gy_gannen = $gy - 1989 +
1;
1186 $gy_offset = $gy_gannen;
1187 if ($gy_gannen == 1)
1189 $gy_offset = '平成'.$gy_offset;
1195 return array( $gy_offset, $gm, $gd );
1199 * Roman number formatting up to 3000
1201 static function romanNumeral( $num ) {
1202 static $table = array(
1203 array( '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ),
1204 array( '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ),
1205 array( '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ),
1206 array( '', 'M', 'MM', 'MMM' )
1209 $num = intval( $num );
1210 if ( $num > 3000 ||
$num <= 0 ) {
1215 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1216 if ( $num >= $pow10 ) {
1217 $s .= $table[$i][floor($num / $pow10)];
1219 $num = $num %
$pow10;
1225 * Hebrew Gematria number formatting up to 9999
1227 static function hebrewNumeral( $num ) {
1228 static $table = array(
1229 array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ),
1230 array( '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ),
1231 array( '', 'ק', 'ר', 'ש', 'ת', 'תק', 'תר', 'תש', 'תת', 'תתק', 'תתר' ),
1232 array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' )
1235 $num = intval( $num );
1236 if ( $num > 9999 ||
$num <= 0 ) {
1241 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1242 if ( $num >= $pow10 ) {
1243 if ( $num == 15 ||
$num == 16 ) {
1244 $s .= $table[0][9] . $table[0][$num - 9];
1247 $s .= $table[$i][intval( ( $num / $pow10 ) )];
1248 if( $pow10 == 1000 ) {
1253 $num = $num %
$pow10;
1255 if( strlen( $s ) == 2 ) {
1258 $str = substr( $s, 0, strlen( $s ) - 2 ) . '"';
1259 $str .= substr( $s, strlen( $s ) - 2, 2 );
1261 $start = substr( $str, 0, strlen( $str ) - 2 );
1262 $end = substr( $str, strlen( $str ) - 2 );
1265 $str = $start . 'ך';
1268 $str = $start . 'ם';
1271 $str = $start . 'ן';
1274 $str = $start . 'ף';
1277 $str = $start . 'ץ';
1284 * This is meant to be used by time(), date(), and timeanddate() to get
1285 * the date preference they're supposed to use, it should be used in
1289 * function timeanddate([...], $format = true) {
1290 * $datePreference = $this->dateFormat($format);
1295 * @param $usePrefs Mixed: if true, the user's preference is used
1296 * if false, the site/language default is used
1297 * if int/string, assumed to be a format.
1300 function dateFormat( $usePrefs = true ) {
1303 if( is_bool( $usePrefs ) ) {
1305 $datePreference = $wgUser->getDatePreference();
1307 $options = User
::getDefaultOptions();
1308 $datePreference = (string)$options['date'];
1311 $datePreference = (string)$usePrefs;
1315 if( $datePreference == '' ) {
1319 return $datePreference;
1323 * @param $ts Mixed: the time format which needs to be turned into a
1324 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
1325 * @param $adj Bool: whether to adjust the time output according to the
1326 * user configured offset ($timecorrection)
1327 * @param $format Mixed: true to use user's date format preference
1328 * @param $timecorrection String: the time offset as returned by
1329 * validateTimeZone() in Special:Preferences
1332 function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
1335 $ts = $this->userAdjust( $ts, $timecorrection );
1338 $pref = $this->dateFormat( $format );
1339 if( $pref == 'default' ||
!isset( $this->dateFormats
["$pref date"] ) ) {
1340 $pref = $this->defaultDateFormat
;
1342 return $this->sprintfDate( $this->dateFormats
["$pref date"], $ts );
1346 * @param $ts Mixed: the time format which needs to be turned into a
1347 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
1348 * @param $adj Bool: whether to adjust the time output according to the
1349 * user configured offset ($timecorrection)
1350 * @param $format Mixed: true to use user's date format preference
1351 * @param $timecorrection String: the time offset as returned by
1352 * validateTimeZone() in Special:Preferences
1355 function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
1358 $ts = $this->userAdjust( $ts, $timecorrection );
1361 $pref = $this->dateFormat( $format );
1362 if( $pref == 'default' ||
!isset( $this->dateFormats
["$pref time"] ) ) {
1363 $pref = $this->defaultDateFormat
;
1365 return $this->sprintfDate( $this->dateFormats
["$pref time"], $ts );
1369 * @param $ts Mixed: the time format which needs to be turned into a
1370 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
1371 * @param $adj Bool: whether to adjust the time output according to the
1372 * user configured offset ($timecorrection)
1373 * @param $format Mixed: what format to return, if it's false output the
1374 * default one (default true)
1375 * @param $timecorrection String: the time offset as returned by
1376 * validateTimeZone() in Special:Preferences
1379 function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false) {
1382 $ts = wfTimestamp( TS_MW
, $ts );
1385 $ts = $this->userAdjust( $ts, $timecorrection );
1388 $pref = $this->dateFormat( $format );
1389 if( $pref == 'default' ||
!isset( $this->dateFormats
["$pref both"] ) ) {
1390 $pref = $this->defaultDateFormat
;
1393 return $this->sprintfDate( $this->dateFormats
["$pref both"], $ts );
1396 function getMessage( $key ) {
1398 return isset( $this->messages
[$key] ) ?
$this->messages
[$key] : null;
1401 function getAllMessages() {
1403 return $this->messages
;
1406 function iconv( $in, $out, $string ) {
1407 # For most languages, this is a wrapper for iconv
1408 return iconv( $in, $out . '//IGNORE', $string );
1411 // callback functions for uc(), lc(), ucwords(), ucwordbreaks()
1412 function ucwordbreaksCallbackAscii($matches){
1413 return $this->ucfirst($matches[1]);
1416 function ucwordbreaksCallbackMB($matches){
1417 return mb_strtoupper($matches[0]);
1420 function ucCallback($matches){
1421 list( $wikiUpperChars ) = self
::getCaseMaps();
1422 return strtr( $matches[1], $wikiUpperChars );
1425 function lcCallback($matches){
1426 list( , $wikiLowerChars ) = self
::getCaseMaps();
1427 return strtr( $matches[1], $wikiLowerChars );
1430 function ucwordsCallbackMB($matches){
1431 return mb_strtoupper($matches[0]);
1434 function ucwordsCallbackWiki($matches){
1435 list( $wikiUpperChars ) = self
::getCaseMaps();
1436 return strtr( $matches[0], $wikiUpperChars );
1439 function ucfirst( $str ) {
1440 if ( empty($str) ) return $str;
1441 if ( ord($str[0]) < 128 ) return ucfirst($str);
1442 else return self
::uc($str,true); // fall back to more complex logic in case of multibyte strings
1445 function uc( $str, $first = false ) {
1446 if ( function_exists( 'mb_strtoupper' ) ) {
1448 if ( self
::isMultibyte( $str ) ) {
1449 return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
1451 return ucfirst( $str );
1454 return self
::isMultibyte( $str ) ?
mb_strtoupper( $str ) : strtoupper( $str );
1457 if ( self
::isMultibyte( $str ) ) {
1458 list( $wikiUpperChars ) = $this->getCaseMaps();
1459 $x = $first ?
'^' : '';
1460 return preg_replace_callback(
1461 "/$x([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
1462 array($this,"ucCallback"),
1466 return $first ?
ucfirst( $str ) : strtoupper( $str );
1471 function lcfirst( $str ) {
1472 if ( empty($str) ) return $str;
1473 if ( is_string( $str ) && ord($str[0]) < 128 ) {
1474 // editing string in place = cool
1475 $str[0]=strtolower($str[0]);
1478 else return self
::lc( $str, true );
1481 function lc( $str, $first = false ) {
1482 if ( function_exists( 'mb_strtolower' ) )
1484 if ( self
::isMultibyte( $str ) )
1485 return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
1487 return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
1489 return self
::isMultibyte( $str ) ?
mb_strtolower( $str ) : strtolower( $str );
1491 if ( self
::isMultibyte( $str ) ) {
1492 list( , $wikiLowerChars ) = self
::getCaseMaps();
1493 $x = $first ?
'^' : '';
1494 return preg_replace_callback(
1495 "/$x([A-Z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
1496 array($this,"lcCallback"),
1500 return $first ?
strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ) : strtolower( $str );
1503 function isMultibyte( $str ) {
1504 return (bool)preg_match( '/[\x80-\xff]/', $str );
1507 function ucwords($str) {
1508 if ( self
::isMultibyte( $str ) ) {
1509 $str = self
::lc($str);
1511 // regexp to find first letter in each word (i.e. after each space)
1512 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
1514 // function to use to capitalize a single char
1515 if ( function_exists( 'mb_strtoupper' ) )
1516 return preg_replace_callback(
1518 array($this,"ucwordsCallbackMB"),
1522 return preg_replace_callback(
1524 array($this,"ucwordsCallbackWiki"),
1529 return ucwords( strtolower( $str ) );
1532 # capitalize words at word breaks
1533 function ucwordbreaks($str){
1534 if (self
::isMultibyte( $str ) ) {
1535 $str = self
::lc($str);
1537 // since \b doesn't work for UTF-8, we explicitely define word break chars
1538 $breaks= "[ \-\(\)\}\{\.,\?!]";
1540 // find first letter after word break
1541 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
1543 if ( function_exists( 'mb_strtoupper' ) )
1544 return preg_replace_callback(
1546 array($this,"ucwordbreaksCallbackMB"),
1550 return preg_replace_callback(
1552 array($this,"ucwordsCallbackWiki"),
1557 return preg_replace_callback(
1558 '/\b([\w\x80-\xff]+)\b/',
1559 array($this,"ucwordbreaksCallbackAscii"),
1564 * Return a case-folded representation of $s
1566 * This is a representation such that caseFold($s1)==caseFold($s2) if $s1
1567 * and $s2 are the same except for the case of their characters. It is not
1568 * necessary for the value returned to make sense when displayed.
1570 * Do *not* perform any other normalisation in this function. If a caller
1571 * uses this function when it should be using a more general normalisation
1572 * function, then fix the caller.
1574 function caseFold( $s ) {
1575 return $this->uc( $s );
1578 function checkTitleEncoding( $s ) {
1579 if( is_array( $s ) ) {
1580 wfDebugDieBacktrace( 'Given array to checkTitleEncoding.' );
1582 # Check for non-UTF-8 URLs
1583 $ishigh = preg_match( '/[\x80-\xff]/', $s);
1584 if(!$ishigh) return $s;
1586 $isutf8 = preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
1587 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s );
1588 if( $isutf8 ) return $s;
1590 return $this->iconv( $this->fallback8bitEncoding(), "utf-8", $s );
1593 function fallback8bitEncoding() {
1595 return $this->fallback8bitEncoding
;
1599 * Some languages have special punctuation to strip out
1600 * or characters which need to be converted for MySQL's
1601 * indexing to grok it correctly. Make such changes here.
1603 * @param $string String
1606 function stripForSearch( $string ) {
1608 if ( $wgDBtype != 'mysql' ) {
1613 wfProfileIn( __METHOD__
);
1615 // MySQL fulltext index doesn't grok utf-8, so we
1616 // need to fold cases and convert to hex
1617 $out = preg_replace_callback(
1618 "/([\\xc0-\\xff][\\x80-\\xbf]*)/",
1619 array( $this, 'stripForSearchCallback' ),
1620 $this->lc( $string ) );
1622 // And to add insult to injury, the default indexing
1623 // ignores short words... Pad them so we can pass them
1624 // through without reconfiguring the server...
1625 $minLength = $this->minSearchLength();
1626 if( $minLength > 1 ) {
1628 $out = preg_replace(
1634 // Periods within things like hostnames and IP addresses
1635 // are also important -- we want a search for "example.com"
1636 // or "192.168.1.1" to work sanely.
1638 // MySQL's search seems to ignore them, so you'd match on
1639 // "example.wikipedia.com" and "192.168.83.1" as well.
1640 $out = preg_replace(
1645 wfProfileOut( __METHOD__
);
1650 * Armor a case-folded UTF-8 string to get through MySQL's
1651 * fulltext search without being mucked up by funny charset
1652 * settings or anything else of the sort.
1654 protected function stripForSearchCallback( $matches ) {
1655 return 'u8' . bin2hex( $matches[1] );
1659 * Check MySQL server's ft_min_word_len setting so we know
1660 * if we need to pad short words...
1662 protected function minSearchLength() {
1663 if( !isset( $this->minSearchLength
) ) {
1664 $sql = "show global variables like 'ft\\_min\\_word\\_len'";
1665 $dbr = wfGetDB( DB_SLAVE
);
1666 $result = $dbr->query( $sql );
1667 $row = $result->fetchObject();
1670 if( $row && $row->Variable_name
== 'ft_min_word_len' ) {
1671 $this->minSearchLength
= intval( $row->Value
);
1673 $this->minSearchLength
= 0;
1676 return $this->minSearchLength
;
1679 function convertForSearchResult( $termsArray ) {
1680 # some languages, e.g. Chinese, need to do a conversion
1681 # in order for search results to be displayed correctly
1686 * Get the first character of a string.
1691 function firstChar( $s ) {
1693 preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
1694 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/', $s, $matches);
1696 if ( isset( $matches[1] ) ) {
1697 if ( strlen( $matches[1] ) != 3 ) {
1701 // Break down Hangul syllables to grab the first jamo
1702 $code = utf8ToCodepoint( $matches[1] );
1703 if ( $code < 0xac00 ||
0xd7a4 <= $code) {
1705 } elseif ( $code < 0xb098 ) {
1706 return "\xe3\x84\xb1";
1707 } elseif ( $code < 0xb2e4 ) {
1708 return "\xe3\x84\xb4";
1709 } elseif ( $code < 0xb77c ) {
1710 return "\xe3\x84\xb7";
1711 } elseif ( $code < 0xb9c8 ) {
1712 return "\xe3\x84\xb9";
1713 } elseif ( $code < 0xbc14 ) {
1714 return "\xe3\x85\x81";
1715 } elseif ( $code < 0xc0ac ) {
1716 return "\xe3\x85\x82";
1717 } elseif ( $code < 0xc544 ) {
1718 return "\xe3\x85\x85";
1719 } elseif ( $code < 0xc790 ) {
1720 return "\xe3\x85\x87";
1721 } elseif ( $code < 0xcc28 ) {
1722 return "\xe3\x85\x88";
1723 } elseif ( $code < 0xce74 ) {
1724 return "\xe3\x85\x8a";
1725 } elseif ( $code < 0xd0c0 ) {
1726 return "\xe3\x85\x8b";
1727 } elseif ( $code < 0xd30c ) {
1728 return "\xe3\x85\x8c";
1729 } elseif ( $code < 0xd558 ) {
1730 return "\xe3\x85\x8d";
1732 return "\xe3\x85\x8e";
1739 function initEncoding() {
1740 # Some languages may have an alternate char encoding option
1741 # (Esperanto X-coding, Japanese furigana conversion, etc)
1742 # If this language is used as the primary content language,
1743 # an override to the defaults can be set here on startup.
1746 function recodeForEdit( $s ) {
1747 # For some languages we'll want to explicitly specify
1748 # which characters make it into the edit box raw
1749 # or are converted in some way or another.
1750 # Note that if wgOutputEncoding is different from
1751 # wgInputEncoding, this text will be further converted
1752 # to wgOutputEncoding.
1753 global $wgEditEncoding;
1754 if( $wgEditEncoding == '' or
1755 $wgEditEncoding == 'UTF-8' ) {
1758 return $this->iconv( 'UTF-8', $wgEditEncoding, $s );
1762 function recodeInput( $s ) {
1763 # Take the previous into account.
1764 global $wgEditEncoding;
1765 if($wgEditEncoding != "") {
1766 $enc = $wgEditEncoding;
1770 if( $enc == 'UTF-8' ) {
1773 return $this->iconv( $enc, 'UTF-8', $s );
1778 * For right-to-left language support
1788 * A hidden direction mark (LRM or RLM), depending on the language direction
1792 function getDirMark() {
1793 return $this->isRTL() ?
"\xE2\x80\x8F" : "\xE2\x80\x8E";
1797 * An arrow, depending on the language direction
1801 function getArrow() {
1802 return $this->isRTL() ?
'←' : '→';
1806 * To allow "foo[[bar]]" to extend the link over the whole word "foobar"
1810 function linkPrefixExtension() {
1812 return $this->linkPrefixExtension
;
1815 function &getMagicWords() {
1817 return $this->magicWords
;
1820 # Fill a MagicWord object with data from here
1821 function getMagic( &$mw ) {
1822 if ( !$this->mMagicHookDone
) {
1823 $this->mMagicHookDone
= true;
1824 wfRunHooks( 'LanguageGetMagic', array( &$this->mMagicExtensions
, $this->getCode() ) );
1826 if ( isset( $this->mMagicExtensions
[$mw->mId
] ) ) {
1827 $rawEntry = $this->mMagicExtensions
[$mw->mId
];
1829 $magicWords =& $this->getMagicWords();
1830 if ( isset( $magicWords[$mw->mId
] ) ) {
1831 $rawEntry = $magicWords[$mw->mId
];
1833 # Fall back to English if local list is incomplete
1834 $magicWords =& Language
::getMagicWords();
1835 if ( !isset($magicWords[$mw->mId
]) ) {
1836 throw new MWException("Magic word '{$mw->mId}' not found" );
1838 $rawEntry = $magicWords[$mw->mId
];
1842 if( !is_array( $rawEntry ) ) {
1843 error_log( "\"$rawEntry\" is not a valid magic thingie for \"$mw->mId\"" );
1845 $mw->mCaseSensitive
= $rawEntry[0];
1846 $mw->mSynonyms
= array_slice( $rawEntry, 1 );
1851 * Add magic words to the extension array
1853 function addMagicWordsByLang( $newWords ) {
1854 $code = $this->getCode();
1855 $fallbackChain = array();
1856 while ( $code && !in_array( $code, $fallbackChain ) ) {
1857 $fallbackChain[] = $code;
1858 $code = self
::getFallbackFor( $code );
1860 if ( !in_array( 'en', $fallbackChain ) ) {
1861 $fallbackChain[] = 'en';
1863 $fallbackChain = array_reverse( $fallbackChain );
1864 foreach ( $fallbackChain as $code ) {
1865 if ( isset( $newWords[$code] ) ) {
1866 $this->mMagicExtensions
= $newWords[$code] +
$this->mMagicExtensions
;
1872 * Get special page names, as an associative array
1873 * case folded alias => real name
1875 function getSpecialPageAliases() {
1878 // Cache aliases because it may be slow to load them
1879 if ( !isset( $this->mExtendedSpecialPageAliases
) ) {
1882 $this->mExtendedSpecialPageAliases
= $this->specialPageAliases
;
1884 global $wgExtensionAliasesFiles;
1885 foreach ( $wgExtensionAliasesFiles as $file ) {
1888 if ( !file_exists($file) )
1889 throw new MWException( "Aliases file does not exist: $file" );
1894 // Check the availability of aliases
1895 if ( !isset($aliases['en']) )
1896 throw new MWException( "Malformed aliases file: $file" );
1898 // Merge all aliases in fallback chain
1899 $code = $this->getCode();
1901 if ( !isset($aliases[$code]) ) continue;
1903 $aliases[$code] = $this->fixSpecialPageAliases( $aliases[$code] );
1904 /* Merge the aliases, THIS will break if there is special page name
1905 * which looks like a numerical key, thanks to PHP...
1906 * See the array_merge_recursive manual entry */
1907 $this->mExtendedSpecialPageAliases
= array_merge_recursive(
1908 $this->mExtendedSpecialPageAliases
, $aliases[$code] );
1910 } while ( $code = self
::getFallbackFor( $code ) );
1913 wfRunHooks( 'LanguageGetSpecialPageAliases',
1914 array( &$this->mExtendedSpecialPageAliases
, $this->getCode() ) );
1917 return $this->mExtendedSpecialPageAliases
;
1921 * Function to fix special page aliases. Will convert the first letter to
1922 * upper case and spaces to underscores. Can be given a full aliases array,
1923 * in which case it will recursively fix all aliases.
1925 public function fixSpecialPageAliases( $mixed ) {
1926 // Work recursively until in string level
1927 if ( is_array($mixed) ) {
1928 $callback = array( $this, 'fixSpecialPageAliases' );
1929 return array_map( $callback, $mixed );
1931 return str_replace( ' ', '_', $this->ucfirst( $mixed ) );
1935 * Italic is unsuitable for some languages
1937 * @param $text String: the text to be emphasized.
1940 function emphasize( $text ) {
1941 return "<em>$text</em>";
1945 * Normally we output all numbers in plain en_US style, that is
1946 * 293,291.235 for twohundredninetythreethousand-twohundredninetyone
1947 * point twohundredthirtyfive. However this is not sutable for all
1948 * languages, some such as Pakaran want ੨੯੩,੨੯੫.੨੩੫ and others such as
1949 * Icelandic just want to use commas instead of dots, and dots instead
1950 * of commas like "293.291,235".
1952 * An example of this function being called:
1954 * wfMsg( 'message', $wgLang->formatNum( $num ) )
1957 * See LanguageGu.php for the Gujarati implementation and
1958 * $separatorTransformTable on MessageIs.php for
1959 * the , => . and . => , implementation.
1961 * @todo check if it's viable to use localeconv() for the decimal
1963 * @param $number Mixed: the string to be formatted, should be an integer
1964 * or a floating point number.
1965 * @param $nocommafy Bool: set to true for special numbers like dates
1968 function formatNum( $number, $nocommafy = false ) {
1969 global $wgTranslateNumerals;
1971 $number = $this->commafy($number);
1972 $s = $this->separatorTransformTable();
1973 if ($s) { $number = strtr($number, $s); }
1976 if ($wgTranslateNumerals) {
1977 $s = $this->digitTransformTable();
1978 if ($s) { $number = strtr($number, $s); }
1984 function parseFormattedNumber( $number ) {
1985 $s = $this->digitTransformTable();
1986 if ($s) { $number = strtr($number, array_flip($s)); }
1988 $s = $this->separatorTransformTable();
1989 if ($s) { $number = strtr($number, array_flip($s)); }
1991 $number = strtr( $number, array (',' => '') );
1996 * Adds commas to a given number
2001 function commafy($_) {
2002 return strrev((string)preg_replace('/(\d{3})(?=\d)(?!\d*\.)/','$1,',strrev($_)));
2005 function digitTransformTable() {
2007 return $this->digitTransformTable
;
2010 function separatorTransformTable() {
2012 return $this->separatorTransformTable
;
2017 * Take a list of strings and build a locale-friendly comma-separated
2018 * list, using the local comma-separator message.
2019 * The last two strings are chained with an "and".
2024 function listToText( $l ) {
2026 $m = count( $l ) - 1;
2028 return $l[0] . $this->getMessageFromDB( 'and' ) . $this->getMessageFromDB( 'word-separator' ) . $l[1];
2031 for ( $i = $m; $i >= 0; $i-- ) {
2034 } else if( $i == $m - 1 ) {
2035 $s = $l[$i] . $this->getMessageFromDB( 'and' ) . $this->getMessageFromDB( 'word-separator' ) . $s;
2037 $s = $l[$i] . $this->getMessageFromDB( 'comma-separator' ) . $s;
2045 * Take a list of strings and build a locale-friendly comma-separated
2046 * list, using the local comma-separator message.
2047 * @param $list array of strings to put in a comma list
2050 function commaList( $list ) {
2053 wfMsgExt( 'comma-separator', array( 'parsemag', 'escapenoentities', 'language' => $this ) ) );
2057 * Take a list of strings and build a locale-friendly semicolon-separated
2058 * list, using the local semicolon-separator message.
2059 * @param $list array of strings to put in a semicolon list
2062 function semicolonList( $list ) {
2065 wfMsgExt( 'semicolon-separator', array( 'parsemag', 'escapenoentities', 'language' => $this ) ) );
2069 * Same as commaList, but separate it with the pipe instead.
2070 * @param $list array of strings to put in a pipe list
2073 function pipeList( $list ) {
2076 wfMsgExt( 'pipe-separator', array( 'escapenoentities', 'language' => $this ) ) );
2080 * Truncate a string to a specified length in bytes, appending an optional
2081 * string (e.g. for ellipses)
2083 * The database offers limited byte lengths for some columns in the database;
2084 * multi-byte character sets mean we need to ensure that only whole characters
2085 * are included, otherwise broken characters can be passed to the user
2087 * If $length is negative, the string will be truncated from the beginning
2089 * @param $string String to truncate
2090 * @param $length Int: maximum length (excluding ellipses)
2091 * @param $ellipsis String to append to the truncated text
2094 function truncate( $string, $length, $ellipsis = '...' ) {
2095 # Use the localized ellipsis character
2096 if( $ellipsis == '...' ) {
2097 $ellipsis = wfMsgExt( 'ellipsis', array( 'escapenoentities', 'language' => $this ) );
2100 if( $length == 0 ) {
2103 if ( strlen( $string ) <= abs( $length ) ) {
2107 $string = substr( $string, 0, $length );
2108 $char = ord( $string[strlen( $string ) - 1] );
2110 if ($char >= 0xc0) {
2111 # We got the first byte only of a multibyte char; remove it.
2112 $string = substr( $string, 0, -1 );
2113 } elseif( $char >= 0x80 &&
2114 preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
2115 '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m ) ) {
2116 # We chopped in the middle of a character; remove it
2119 return $string . $ellipsis;
2121 $string = substr( $string, $length );
2122 $char = ord( $string[0] );
2123 if( $char >= 0x80 && $char < 0xc0 ) {
2124 # We chopped in the middle of a character; remove the whole thing
2125 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
2127 return $ellipsis . $string;
2132 * Grammatical transformations, needed for inflected languages
2133 * Invoked by putting {{grammar:case|word}} in a message
2135 * @param $word string
2136 * @param $case string
2139 function convertGrammar( $word, $case ) {
2140 global $wgGrammarForms;
2141 if ( isset($wgGrammarForms[$this->getCode()][$case][$word]) ) {
2142 return $wgGrammarForms[$this->getCode()][$case][$word];
2148 * Provides an alternative text depending on specified gender.
2149 * Usage {{gender:username|masculine|feminine|neutral}}.
2150 * username is optional, in which case the gender of current user is used,
2151 * but only in (some) interface messages; otherwise default gender is used.
2152 * If second or third parameter are not specified, masculine is used.
2153 * These details may be overriden per language.
2155 function gender( $gender, $forms ) {
2156 if ( !count($forms) ) { return ''; }
2157 $forms = $this->preConvertPlural( $forms, 2 );
2158 if ( $gender === 'male' ) return $forms[0];
2159 if ( $gender === 'female' ) return $forms[1];
2160 return isset($forms[2]) ?
$forms[2] : $forms[0];
2164 * Plural form transformations, needed for some languages.
2165 * For example, there are 3 form of plural in Russian and Polish,
2166 * depending on "count mod 10". See [[w:Plural]]
2167 * For English it is pretty simple.
2169 * Invoked by putting {{plural:count|wordform1|wordform2}}
2170 * or {{plural:count|wordform1|wordform2|wordform3}}
2172 * Example: {{plural:{{NUMBEROFARTICLES}}|article|articles}}
2174 * @param $count Integer: non-localized number
2175 * @param $forms Array: different plural forms
2176 * @return string Correct form of plural for $count in this language
2178 function convertPlural( $count, $forms ) {
2179 if ( !count($forms) ) { return ''; }
2180 $forms = $this->preConvertPlural( $forms, 2 );
2182 return ( $count == 1 ) ?
$forms[0] : $forms[1];
2186 * Checks that convertPlural was given an array and pads it to requested
2187 * amound of forms by copying the last one.
2189 * @param $count Integer: How many forms should there be at least
2190 * @param $forms Array of forms given to convertPlural
2191 * @return array Padded array of forms or an exception if not an array
2193 protected function preConvertPlural( /* Array */ $forms, $count ) {
2194 while ( count($forms) < $count ) {
2195 $forms[] = $forms[count($forms)-1];
2201 * For translaing of expiry times
2202 * @param $str String: the validated block time in English
2203 * @return Somehow translated block time
2204 * @see LanguageFi.php for example implementation
2206 function translateBlockExpiry( $str ) {
2208 $scBlockExpiryOptions = $this->getMessageFromDB( 'ipboptions' );
2210 if ( $scBlockExpiryOptions == '-') {
2214 foreach (explode(',', $scBlockExpiryOptions) as $option) {
2215 if ( strpos($option, ":") === false )
2217 list($show, $value) = explode(":", $option);
2218 if ( strcmp ( $str, $value) == 0 ) {
2219 return htmlspecialchars( trim( $show ) );
2227 * languages like Chinese need to be segmented in order for the diff
2230 * @param $text String
2233 function segmentForDiff( $text ) {
2238 * and unsegment to show the result
2240 * @param $text String
2243 function unsegmentForDiff( $text ) {
2247 # convert text to all supported variants
2248 function autoConvertToAllVariants($text) {
2249 return $this->mConverter
->autoConvertToAllVariants($text);
2252 # convert text to different variants of a language.
2253 function convert( $text, $isTitle = false) {
2254 return $this->mConverter
->convert($text, $isTitle);
2257 # Convert text from within Parser
2258 function parserConvert( $text, &$parser ) {
2259 return $this->mConverter
->parserConvert( $text, $parser );
2262 # Check if this is a language with variants
2263 function hasVariants(){
2264 return sizeof($this->getVariants())>1;
2267 # Put custom tags (e.g. -{ }-) around math to prevent conversion
2268 function armourMath($text){
2269 return $this->mConverter
->armourMath($text);
2274 * Perform output conversion on a string, and encode for safe HTML output.
2275 * @param $text String
2276 * @param $isTitle Bool -- wtf?
2278 * @todo this should get integrated somewhere sane
2280 function convertHtml( $text, $isTitle = false ) {
2281 return htmlspecialchars( $this->convert( $text, $isTitle ) );
2284 function convertCategoryKey( $key ) {
2285 return $this->mConverter
->convertCategoryKey( $key );
2289 * get the list of variants supported by this langauge
2290 * see sample implementation in LanguageZh.php
2292 * @return array an array of language codes
2294 function getVariants() {
2295 return $this->mConverter
->getVariants();
2299 function getPreferredVariant( $fromUser = true ) {
2300 return $this->mConverter
->getPreferredVariant( $fromUser );
2304 * if a language supports multiple variants, it is
2305 * possible that non-existing link in one variant
2306 * actually exists in another variant. this function
2307 * tries to find it. See e.g. LanguageZh.php
2309 * @param $link String: the name of the link
2310 * @param $nt Mixed: the title object of the link
2311 * @param boolean $ignoreOtherCond: to disable other conditions when
2312 * we need to transclude a template or update a category's link
2313 * @return null the input parameters may be modified upon return
2315 function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
2316 $this->mConverter
->findVariantLink( $link, $nt, $ignoreOtherCond );
2320 * If a language supports multiple variants, converts text
2321 * into an array of all possible variants of the text:
2322 * 'variant' => text in that variant
2324 function convertLinkToAllVariants($text){
2325 return $this->mConverter
->convertLinkToAllVariants($text);
2330 * returns language specific options used by User::getPageRenderHash()
2331 * for example, the preferred language variant
2335 function getExtraHashOptions() {
2336 return $this->mConverter
->getExtraHashOptions();
2340 * for languages that support multiple variants, the title of an
2341 * article may be displayed differently in different variants. this
2342 * function returns the apporiate title defined in the body of the article.
2346 function getParsedTitle() {
2347 return $this->mConverter
->getParsedTitle();
2351 * Enclose a string with the "no conversion" tag. This is used by
2352 * various functions in the Parser
2354 * @param $text String: text to be tagged for no conversion
2356 * @return string the tagged text
2358 function markNoConversion( $text, $noParse=false ) {
2359 return $this->mConverter
->markNoConversion( $text, $noParse );
2363 * Callback function for magicword 'groupconvert'
2365 * @param string $group: the group name called for
2366 * @return blank string
2368 function groupConvert( $group ) {
2369 return $this->mConverter
->groupConvert( $group );
2373 * A regular expression to match legal word-trailing characters
2374 * which should be merged onto a link of the form [[foo]]bar.
2378 function linkTrail() {
2380 return $this->linkTrail
;
2383 function getLangObj() {
2388 * Get the RFC 3066 code for this language object
2390 function getCode() {
2391 return $this->mCode
;
2394 function setCode( $code ) {
2395 $this->mCode
= $code;
2398 static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
2399 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
2402 static function getMessagesFileName( $code ) {
2404 return self
::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
2407 static function getClassFileName( $code ) {
2409 return self
::getFileName( "$IP/languages/classes/Language", $code, '.php' );
2412 static function getLocalisationArray( $code, $disableCache = false ) {
2413 self
::loadLocalisation( $code, $disableCache );
2414 return self
::$mLocalisationCache[$code];
2418 * Load localisation data for a given code into the static cache
2420 * @return array Dependencies, map of filenames to mtimes
2422 static function loadLocalisation( $code, $disableCache = false ) {
2423 static $recursionGuard = array();
2424 global $wgMemc, $wgEnableSerializedMessages, $wgCheckSerialized;
2427 throw new MWException( "Invalid language code requested" );
2430 if ( !$disableCache ) {
2431 # Try the per-process cache
2432 if ( isset( self
::$mLocalisationCache[$code] ) ) {
2433 return self
::$mLocalisationCache[$code]['deps'];
2436 wfProfileIn( __METHOD__
);
2438 # Try the serialized directory
2439 if( $wgEnableSerializedMessages ) {
2440 $cache = wfGetPrecompiledData( self
::getFileName( "Messages", $code, '.ser' ) );
2442 if ( $wgCheckSerialized && self
::isLocalisationOutOfDate( $cache ) ) {
2444 wfDebug( "Language::loadLocalisation(): precompiled data file for $code is out of date\n" );
2446 self
::$mLocalisationCache[$code] = $cache;
2447 wfDebug( "Language::loadLocalisation(): got localisation for $code from precompiled data file\n" );
2448 wfProfileOut( __METHOD__
);
2449 return self
::$mLocalisationCache[$code]['deps'];
2456 # Try the global cache
2457 $memcKey = wfMemcKey('localisation', $code );
2458 $fbMemcKey = wfMemcKey('fallback', $cache['fallback'] );
2459 $cache = $wgMemc->get( $memcKey );
2461 if ( self
::isLocalisationOutOfDate( $cache ) ) {
2462 $wgMemc->delete( $memcKey );
2463 $wgMemc->delete( $fbMemcKey );
2465 wfDebug( "Language::loadLocalisation(): localisation cache for $code had expired\n" );
2467 self
::$mLocalisationCache[$code] = $cache;
2468 wfDebug( "Language::loadLocalisation(): got localisation for $code from cache\n" );
2469 wfProfileOut( __METHOD__
);
2470 return $cache['deps'];
2474 wfProfileIn( __METHOD__
);
2477 # Default fallback, may be overridden when the messages file is included
2478 if ( $code != 'en' ) {
2484 # Load the primary localisation from the source file
2485 $filename = self
::getMessagesFileName( $code );
2486 if ( !file_exists( $filename ) ) {
2487 wfDebug( "Language::loadLocalisation(): no localisation file for $code, using implicit fallback to en\n" );
2488 $cache = compact( self
::$mLocalisationKeys ); // Set correct fallback
2491 $deps = array( $filename => filemtime( $filename ) );
2492 require( $filename );
2493 $cache = compact( self
::$mLocalisationKeys );
2494 wfDebug( "Language::loadLocalisation(): got localisation for $code from source\n" );
2497 # Load magic word source file
2499 $filename = "$IP/includes/MagicWord.php";
2500 $newDeps = array( $filename => filemtime( $filename ) );
2501 $deps = array_merge( $deps, $newDeps );
2503 if ( !empty( $fallback ) ) {
2504 # Load the fallback localisation, with a circular reference guard
2505 if ( isset( $recursionGuard[$code] ) ) {
2506 throw new MWException( "Error: Circular fallback reference in language code $code" );
2508 $recursionGuard[$code] = true;
2509 $newDeps = self
::loadLocalisation( $fallback, $disableCache );
2510 unset( $recursionGuard[$code] );
2512 $secondary = self
::$mLocalisationCache[$fallback];
2513 $deps = array_merge( $deps, $newDeps );
2515 # Merge the fallback localisation with the current localisation
2516 foreach ( self
::$mLocalisationKeys as $key ) {
2517 if ( isset( $cache[$key] ) ) {
2518 if ( isset( $secondary[$key] ) ) {
2519 if ( in_array( $key, self
::$mMergeableMapKeys ) ) {
2520 $cache[$key] = $cache[$key] +
$secondary[$key];
2521 } elseif ( in_array( $key, self
::$mMergeableListKeys ) ) {
2522 $cache[$key] = array_merge( $secondary[$key], $cache[$key] );
2523 } elseif ( in_array( $key, self
::$mMergeableAliasListKeys ) ) {
2524 $cache[$key] = array_merge_recursive( $cache[$key], $secondary[$key] );
2528 $cache[$key] = $secondary[$key];
2532 # Merge bookstore lists if requested
2533 if ( !empty( $cache['bookstoreList']['inherit'] ) ) {
2534 $cache['bookstoreList'] = array_merge( $cache['bookstoreList'], $secondary['bookstoreList'] );
2536 if ( isset( $cache['bookstoreList']['inherit'] ) ) {
2537 unset( $cache['bookstoreList']['inherit'] );
2541 # Add dependencies to the cache entry
2542 $cache['deps'] = $deps;
2544 # Replace spaces with underscores in namespace names
2545 $cache['namespaceNames'] = str_replace( ' ', '_', $cache['namespaceNames'] );
2547 # And do the same for specialpage aliases. $page is an array.
2548 foreach ( $cache['specialPageAliases'] as &$page ) {
2549 $page = str_replace( ' ', '_', $page );
2551 # Decouple the reference to prevent accidental damage
2554 # Save to both caches
2555 self
::$mLocalisationCache[$code] = $cache;
2556 if ( !$disableCache ) {
2557 $wgMemc->set( $memcKey, $cache );
2558 $wgMemc->set( $fbMemcKey, (string) $cache['fallback'] );
2561 wfProfileOut( __METHOD__
);
2566 * Test if a given localisation cache is out of date with respect to the
2567 * source Messages files. This is done automatically for the global cache
2568 * in $wgMemc, but is only done on certain occasions for the serialized
2571 * @param $cache mixed Either a language code or a cache array
2573 static function isLocalisationOutOfDate( $cache ) {
2574 if ( !is_array( $cache ) ) {
2575 self
::loadLocalisation( $cache );
2576 $cache = self
::$mLocalisationCache[$cache];
2578 // At least one language file and the MagicWord file needed
2579 if( count($cache['deps']) < 2 ) {
2583 foreach ( $cache['deps'] as $file => $mtime ) {
2584 if ( !file_exists( $file ) ||
filemtime( $file ) > $mtime ) {
2593 * Get the fallback for a given language
2595 static function getFallbackFor( $code ) {
2597 if ( $code === 'en' ) return false;
2600 static $cache = array();
2602 if ( isset($cache[$code]) ) return $cache[$code];
2606 $memcKey = wfMemcKey( 'fallback', $code );
2607 $fbcode = $wgMemc->get( $memcKey );
2609 if ( is_string($fbcode) ) {
2610 // False is stored as a string to detect failures in memcache properly
2611 if ( $fbcode === '' ) $fbcode = false;
2613 // Update local cache and return
2614 $cache[$code] = $fbcode;
2618 // Nothing in caches, load and and update both caches
2619 self
::loadLocalisation( $code );
2620 $fbcode = self
::$mLocalisationCache[$code]['fallback'];
2622 $cache[$code] = $fbcode;
2623 $wgMemc->set( $memcKey, (string) $fbcode );
2629 * Get all messages for a given language
2631 static function getMessagesFor( $code ) {
2632 self
::loadLocalisation( $code );
2633 return self
::$mLocalisationCache[$code]['messages'];
2637 * Get a message for a given language
2639 static function getMessageFor( $key, $code ) {
2640 self
::loadLocalisation( $code );
2641 return isset( self
::$mLocalisationCache[$code]['messages'][$key] ) ? self
::$mLocalisationCache[$code]['messages'][$key] : null;
2645 * Load localisation data for this object
2648 if ( !$this->mLoaded
) {
2649 self
::loadLocalisation( $this->getCode() );
2650 $cache =& self
::$mLocalisationCache[$this->getCode()];
2651 foreach ( self
::$mLocalisationKeys as $key ) {
2652 $this->$key = $cache[$key];
2654 $this->mLoaded
= true;
2656 $this->fixUpSettings();
2661 * Do any necessary post-cache-load settings adjustment
2663 function fixUpSettings() {
2664 global $wgExtraNamespaces, $wgMetaNamespace, $wgMetaNamespaceTalk,
2665 $wgNamespaceAliases, $wgAmericanDates;
2666 wfProfileIn( __METHOD__
);
2667 if ( $wgExtraNamespaces ) {
2668 $this->namespaceNames
= $wgExtraNamespaces +
$this->namespaceNames
;
2671 $this->namespaceNames
[NS_PROJECT
] = $wgMetaNamespace;
2672 if ( $wgMetaNamespaceTalk ) {
2673 $this->namespaceNames
[NS_PROJECT_TALK
] = $wgMetaNamespaceTalk;
2675 $talk = $this->namespaceNames
[NS_PROJECT_TALK
];
2676 $this->namespaceNames
[NS_PROJECT_TALK
] =
2677 $this->fixVariableInNamespace( $talk );
2680 # The above mixing may leave namespaces out of canonical order.
2681 # Re-order by namespace ID number...
2682 ksort( $this->namespaceNames
);
2684 # Put namespace names and aliases into a hashtable.
2685 # If this is too slow, then we should arrange it so that it is done
2686 # before caching. The catch is that at pre-cache time, the above
2687 # class-specific fixup hasn't been done.
2688 $this->mNamespaceIds
= array();
2689 foreach ( $this->namespaceNames
as $index => $name ) {
2690 $this->mNamespaceIds
[$this->lc($name)] = $index;
2692 if ( $this->namespaceAliases
) {
2693 foreach ( $this->namespaceAliases
as $name => $index ) {
2694 if ( $index === NS_PROJECT_TALK
) {
2695 unset( $this->namespaceAliases
[$name] );
2696 $name = $this->fixVariableInNamespace( $name );
2697 $this->namespaceAliases
[$name] = $index;
2699 $this->mNamespaceIds
[$this->lc($name)] = $index;
2702 if ( $wgNamespaceAliases ) {
2703 foreach ( $wgNamespaceAliases as $name => $index ) {
2704 $this->mNamespaceIds
[$this->lc($name)] = $index;
2708 if ( $this->defaultDateFormat
== 'dmy or mdy' ) {
2709 $this->defaultDateFormat
= $wgAmericanDates ?
'mdy' : 'dmy';
2711 wfProfileOut( __METHOD__
);
2714 function fixVariableInNamespace( $talk ) {
2715 if ( strpos( $talk, '$1' ) === false ) return $talk;
2717 global $wgMetaNamespace;
2718 $talk = str_replace( '$1', $wgMetaNamespace, $talk );
2720 # Allow grammar transformations
2721 # Allowing full message-style parsing would make simple requests
2722 # such as action=raw much more expensive than they need to be.
2723 # This will hopefully cover most cases.
2724 $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
2725 array( &$this, 'replaceGrammarInNamespace' ), $talk );
2726 return str_replace( ' ', '_', $talk );
2729 function replaceGrammarInNamespace( $m ) {
2730 return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
2733 static function getCaseMaps() {
2734 static $wikiUpperChars, $wikiLowerChars;
2735 if ( isset( $wikiUpperChars ) ) {
2736 return array( $wikiUpperChars, $wikiLowerChars );
2739 wfProfileIn( __METHOD__
);
2740 $arr = wfGetPrecompiledData( 'Utf8Case.ser' );
2741 if ( $arr === false ) {
2742 throw new MWException(
2743 "Utf8Case.ser is missing, please run \"make\" in the serialized directory\n" );
2746 wfProfileOut( __METHOD__
);
2747 return array( $wikiUpperChars, $wikiLowerChars );
2750 function formatTimePeriod( $seconds ) {
2751 if ( $seconds < 10 ) {
2752 return $this->formatNum( sprintf( "%.1f", $seconds ) ) . wfMsg( 'seconds-abbrev' );
2753 } elseif ( $seconds < 60 ) {
2754 return $this->formatNum( round( $seconds ) ) . wfMsg( 'seconds-abbrev' );
2755 } elseif ( $seconds < 3600 ) {
2756 return $this->formatNum( floor( $seconds / 60 ) ) . wfMsg( 'minutes-abbrev' ) .
2757 $this->formatNum( round( fmod( $seconds, 60 ) ) ) . wfMsg( 'seconds-abbrev' );
2759 $hours = floor( $seconds / 3600 );
2760 $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
2761 $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
2762 return $this->formatNum( $hours ) . wfMsg( 'hours-abbrev' ) .
2763 $this->formatNum( $minutes ) . wfMsg( 'minutes-abbrev' ) .
2764 $this->formatNum( $secondsPart ) . wfMsg( 'seconds-abbrev' );
2768 function formatBitrate( $bps ) {
2769 $units = array( 'bps', 'kbps', 'Mbps', 'Gbps' );
2771 return $this->formatNum( $bps ) . $units[0];
2773 $unitIndex = floor( log10( $bps ) / 3 );
2774 $mantissa = $bps / pow( 1000, $unitIndex );
2775 if ( $mantissa < 10 ) {
2776 $mantissa = round( $mantissa, 1 );
2778 $mantissa = round( $mantissa );
2780 return $this->formatNum( $mantissa ) . $units[$unitIndex];
2784 * Format a size in bytes for output, using an appropriate
2785 * unit (B, KB, MB or GB) according to the magnitude in question
2787 * @param $size Size to format
2788 * @return string Plain text (not HTML)
2790 function formatSize( $size ) {
2791 // For small sizes no decimal places necessary
2793 if( $size > 1024 ) {
2794 $size = $size / 1024;
2795 if( $size > 1024 ) {
2796 $size = $size / 1024;
2797 // For MB and bigger two decimal places are smarter
2799 if( $size > 1024 ) {
2800 $size = $size / 1024;
2801 $msg = 'size-gigabytes';
2803 $msg = 'size-megabytes';
2806 $msg = 'size-kilobytes';
2809 $msg = 'size-bytes';
2811 $size = round( $size, $round );
2812 $text = $this->getMessageFromDB( $msg );
2813 return str_replace( '$1', $this->formatNum( $size ), $text );